package com.tcm.websocket.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tcm.common.handler.GlobalExceptionHandler;
import com.tcm.config.AppProperties;
import com.tcm.entity.dto.MessageDto;
import com.tcm.entity.dto.param.BattleItemCorrectParam;
import com.tcm.entity.resp.Message;
import com.tcm.entity.vo.BattleItemDescribeResp;
import com.tcm.entity.vo.BattleRoomInfoResp;
import com.tcm.entity.vo.BattleTopicResp;
import com.tcm.exception.BattleWsHandleException;
import com.tcm.service.BattleCoreService;
import com.tcm.websocket.WebSocket;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Guqier
 * @version 1.0
 * @date 2023/9/14 21:36
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class BattleWebSocketImpl implements WebSocket {

    /**
     * 在线连接数（线程安全）
     */
    private final AtomicInteger connectionCount = new AtomicInteger(0);

    /**
     * 线程安全的无序集合（存储会话）
     */
    private final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();

    private final BattleCoreService battleCoreService;

    @Override
    public void handleOpen(WebSocketSession session) throws IOException {
        int count = connectionCount.incrementAndGet();
        Long userId = (Long) session.getAttributes().get("userId");
        Optional<WebSocketSession> optional = sessions.stream().filter(s -> s.getAttributes().get("userId").equals(userId)).findFirst();
        if (optional.isPresent()) {
            optional.get().close();
        }
        sessions.add(session);
        sessions.stream().map(WebSocketSession::hashCode).forEach(System.out::println);
        log.info("新的WebSocket建立连接，当前在线数：{}", count);
    }

    @Override
    public void handleClose(WebSocketSession session) {
        Long userId = (Long) session.getAttributes().get("userId");
        List<MessageDto> dtos = battleCoreService.exit(userId);
        if (dtos != null) {
            for (MessageDto dto : dtos) {
                try {
                    sendMessage(String.valueOf(dto.getUserId()), dto.getMessage().toJson());
                } catch (IOException e) {
                    log.error("WebSocket异常栈：{}\n", e.getMessage());
                    log.error(GlobalExceptionHandler.getTraceToString(e));
                }
            }
        }
        sessions.remove(session);
        int count = connectionCount.decrementAndGet();
        log.info("已有WebSocket断开连接，当前在线数：{}", count);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void handleMessage(WebSocketSession session, String msg) {
        // 只处理前端传来的文本消息，并且直接丢弃了客户端传来的消息
        log.info("接收到消息：{}", msg);
        Long userId = (Long) session.getAttributes().get("userId");
        try {
            try {
                // 反序列化数据
                Message message = this.readMessage(msg);
                // 判断消息类型
                switch (message.getCode()) {
                    // 处理心跳消息
                    case AppProperties.BattleMsgCode.PING: {
                        this.sendMessage(session, new Message().setCode("ping").setMsg("心跳响应").toJson());
                        break;
                    }
                    // 开始匹配
                    case AppProperties.BattleMsgCode.START_MATCH: {
                        List<MessageDto> messageDtos = battleCoreService.startMatch(userId);
                        if (messageDtos != null) {
                            for (MessageDto messageDto : messageDtos) {
                                this.sendMessage(messageDto.getUserId().toString(), messageDto.getMessage().toJson());
                            }
                        }
                        break;
                    }
                    // 获取题目
                    case AppProperties.BattleMsgCode.TOPIC: {
                        String courseId = ((LinkedHashMap<String, String>) message.getData()).get("courseId");
                        BattleTopicResp topic = battleCoreService.getTopic(userId, Long.parseLong(courseId));
                        this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.TOPIC).setMsg("success").setData(topic).toJson());
                        break;
                    }
                    // 提交答案
                    case AppProperties.BattleMsgCode.CORRECT: {
                        LinkedHashMap<String, String> paramMap = (LinkedHashMap<String, String>) message.getData();
                        System.out.println(paramMap);
                        BattleItemCorrectParam param = new BattleItemCorrectParam(Long.parseLong(paramMap.get("id")), paramMap.get("ans"), Integer.valueOf(paramMap.get("seconds")));
                        List<MessageDto> messageDtos = battleCoreService.correctTopic(param, userId);
                        if (messageDtos != null) {
                            for (MessageDto messageDto : messageDtos) {
                                this.sendMessage(messageDto.getUserId().toString(), messageDto.getMessage().toJson());
                            }
                        }
                        break;
                    }
                    // 获取对战详细信息
                    case AppProperties.BattleMsgCode.DETAIL: {
                        List<MessageDto> messageDtoList = battleCoreService.getBattleDetail(userId);
                        for (MessageDto messageDto : messageDtoList) {
                            this.sendMessage(String.valueOf(messageDto.getUserId()), messageDto.getMessage().toJson());
                        }
                        break;
                    }
                    // 获取所有题目解析
                    case AppProperties.BattleMsgCode.DESCRIBE: {
                        List<BattleItemDescribeResp> describes = battleCoreService.getDescribes(userId);
                        this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.DESCRIBE).setMsg("success").setData(describes).toJson());
                        break;
                    }
                    // 退出匹配
                    case AppProperties.BattleMsgCode.EXIT_MATCH: {
                        Boolean b = battleCoreService.exitMatch(userId);
                        this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.EXIT_MATCH).setMsg(b ? "success" : "匹配信息不存在").toJson());
                        break;
                    }
                    // 创建邀请对战房间
                    case AppProperties.BattleMsgCode.CREATE_ROOM: {
                        LinkedHashMap<String, String> paramMap = (LinkedHashMap<String, String>) message.getData();
                        Map<String, String> res = battleCoreService.createBattleRoom(userId, Integer.valueOf(paramMap.get("maxNum")));
                        this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.CREATE_ROOM).setMsg("success").setData(res).toJson());
                        break;
                    }
                    // 获取房间邀请码
                    case AppProperties.BattleMsgCode.INVITATION_CODE: {
                        LinkedHashMap<String, String> paramMap = (LinkedHashMap<String, String>) message.getData();
                        Map<String, String> map = battleCoreService.getRoomInvitationCode(Long.valueOf(paramMap.get("roomId")), userId);
                        this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.INVITATION_CODE).setMsg("success").setData(map).toJson());
                        break;
                    }
                    // 加入房间
                    case AppProperties.BattleMsgCode.JOIN_ROOM: {
                        LinkedHashMap<String, String> paramMap = (LinkedHashMap<String, String>) message.getData();
                        List<MessageDto> messageDtos = battleCoreService.joinRoom(userId, paramMap.get("code"));
                        for (MessageDto messageDto : messageDtos) {
                            this.sendMessage(String.valueOf(messageDto.getUserId()), messageDto.getMessage().toJson());
                        }
                        break;
                    }
                    // 开始房间对战
                    case AppProperties.BattleMsgCode.START: {
                        List<MessageDto> messageDtos = battleCoreService.startRoomBattle(userId);
                        for (MessageDto messageDto : messageDtos) {
                            this.sendMessage(String.valueOf(messageDto.getUserId()), messageDto.getMessage().toJson());
                        }
                        break;
                    }
                    // 获取房间信息
                    case AppProperties.BattleMsgCode.ROOM_INFO: {
                        BattleRoomInfoResp roomInfo = battleCoreService.getRoomInfo(userId);
                        this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.ROOM_INFO).setMsg("success").setData(roomInfo).toJson());
                        break;
                    }
                    default: {
                        throw new BattleWsHandleException(userId, "未知请求类型：" + message.getCode());
                    }
                }
            } catch (BattleWsHandleException e) {
                this.sendMessage(String.valueOf(e.getUserId()), new Message().setCode(AppProperties.BattleMsgCode.ERROR).setMsg(e.getMessage()).toJson());
            }catch (Exception e){
                this.sendMessage(String.valueOf(userId), new Message().setCode(AppProperties.BattleMsgCode.ERROR).setMsg("系统异常").toJson());
                log.error(GlobalExceptionHandler.getTraceToString(e));
            }
        } catch (Exception e) {
            log.error("WebSocket异常栈：{}\n", e.getMessage());
            log.error(GlobalExceptionHandler.getTraceToString(e));
        }
    }

    @Override
    public void sendMessage(WebSocketSession session, String message) throws IOException {
        this.sendMessage(session, new TextMessage(message));
    }

    @Override
    public void sendMessage(String userId, TextMessage message) throws IOException {
        Optional<WebSocketSession> userSession = sessions.stream().filter(session -> {
            if (!session.isOpen()) {
                return false;
            }
            Map<String, Object> attributes = session.getAttributes();
            if (!attributes.containsKey("userId")) {
                return false;
            }
            String uid = ((Long) attributes.get("userId")).toString();
            return uid.equals(userId);
        }).findFirst();
        if (userSession.isPresent()) {
            userSession.get().sendMessage(message);
        }
    }

    @Override
    public void sendMessage(String userId, String message) throws IOException {
        this.sendMessage(userId, new TextMessage(message));
    }

    @Override
    public void sendMessage(WebSocketSession session, TextMessage message) throws IOException {
        session.sendMessage(message);
    }

    @Override
    public void broadCast(String message) throws IOException {
        for (WebSocketSession session : sessions) {
            if (!session.isOpen()) {
                continue;
            }
            this.sendMessage(session, message);
        }
    }

    @Override
    public void broadCast(TextMessage message) throws IOException {
        for (WebSocketSession session : sessions) {
            if (!session.isOpen()) {
                continue;
            }
            session.sendMessage(message);
        }
    }

    @Override
    public void handleError(WebSocketSession session, Throwable error) {
        log.error("websocket error：{}，session id：{}", error.getMessage(), session.getId());
        log.error("", error);
    }

    @Override
    public Set<WebSocketSession> getSessions() {
        return sessions;
    }

    @Override
    public int getConnectionCount() {
        return connectionCount.get();
    }

    /**
     * 读取消息数据
     * @param message JSON格式消息文本
     * @return Message对象
     */
    private Message readMessage(String message) throws JsonProcessingException {
        return new ObjectMapper().readValue(message, Message.class);
    }

}
